// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/views/controls/native/native_view_host_aura.h"

#include "base/logging.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/hit_test.h"
#include "ui/views/controls/native/native_view_host.h"
#include "ui/views/view_constants_aura.h"
#include "ui/views/widget/widget.h"

namespace views {

class NativeViewHostAura::ClippingWindowDelegate : public aura::WindowDelegate {
public:
    ClippingWindowDelegate()
        : native_view_(NULL)
    {
    }
    ~ClippingWindowDelegate() override { }

    void set_native_view(aura::Window* native_view)
    {
        native_view_ = native_view;
    }

    gfx::Size GetMinimumSize() const override { return gfx::Size(); }
    gfx::Size GetMaximumSize() const override { return gfx::Size(); }
    void OnBoundsChanged(const gfx::Rect& old_bounds,
        const gfx::Rect& new_bounds) override { }
    gfx::NativeCursor GetCursor(const gfx::Point& point) override
    {
        return gfx::kNullCursor;
    }
    int GetNonClientComponent(const gfx::Point& point) const override
    {
        return HTCLIENT;
    }
    bool ShouldDescendIntoChildForEventHandling(
        aura::Window* child,
        const gfx::Point& location) override
    {
        return true;
    }
    bool CanFocus() override
    {
        // Ask the hosted native view's delegate because directly calling
        // aura::Window::CanFocus() will call back into this when checking whether
        // parents can focus.
        return native_view_ && native_view_->delegate()
            ? native_view_->delegate()->CanFocus()
            : true;
    }
    void OnCaptureLost() override { }
    void OnPaint(const ui::PaintContext& context) override { }
    void OnDeviceScaleFactorChanged(float device_scale_factor) override { }
    void OnWindowDestroying(aura::Window* window) override { }
    void OnWindowDestroyed(aura::Window* window) override { }
    void OnWindowTargetVisibilityChanged(bool visible) override { }
    bool HasHitTestMask() const override { return false; }
    void GetHitTestMask(gfx::Path* mask) const override { }

private:
    aura::Window* native_view_;
};

NativeViewHostAura::NativeViewHostAura(NativeViewHost* host)
    : host_(host)
    , clipping_window_delegate_(new ClippingWindowDelegate())
    , clipping_window_(clipping_window_delegate_.get())
{
    // Set the type so descendant views (including popups) get positioned
    // appropriately.
    clipping_window_.SetType(ui::wm::WINDOW_TYPE_CONTROL);
    clipping_window_.Init(ui::LAYER_NOT_DRAWN);
    clipping_window_.set_owned_by_parent(false);
    clipping_window_.SetName("NativeViewHostAuraClip");
    clipping_window_.layer()->SetMasksToBounds(true);
    clipping_window_.SetProperty(views::kHostViewKey, static_cast<View*>(host_));
}

NativeViewHostAura::~NativeViewHostAura()
{
    if (host_->native_view()) {
        host_->native_view()->RemoveObserver(this);
        host_->native_view()->ClearProperty(views::kHostViewKey);
        host_->native_view()->ClearProperty(aura::client::kHostWindowKey);
        clipping_window_.ClearProperty(views::kHostViewKey);
        if (host_->native_view()->parent() == &clipping_window_)
            clipping_window_.RemoveChild(host_->native_view());
    }
}

////////////////////////////////////////////////////////////////////////////////
// NativeViewHostAura, NativeViewHostWrapper implementation:
void NativeViewHostAura::AttachNativeView()
{
    clipping_window_delegate_->set_native_view(host_->native_view());
    host_->native_view()->AddObserver(this);
    host_->native_view()->SetProperty(views::kHostViewKey,
        static_cast<View*>(host_));
    AddClippingWindow();
}

void NativeViewHostAura::NativeViewDetaching(bool destroyed)
{
    clipping_window_delegate_->set_native_view(NULL);
    RemoveClippingWindow();
    if (!destroyed) {
        host_->native_view()->RemoveObserver(this);
        host_->native_view()->ClearProperty(views::kHostViewKey);
        host_->native_view()->ClearProperty(aura::client::kHostWindowKey);
        host_->native_view()->Hide();
        if (host_->native_view()->parent())
            Widget::ReparentNativeView(host_->native_view(), NULL);
    }
}

void NativeViewHostAura::AddedToWidget()
{
    if (!host_->native_view())
        return;

    AddClippingWindow();
    if (host_->IsDrawn())
        host_->native_view()->Show();
    else
        host_->native_view()->Hide();
    host_->Layout();
}

void NativeViewHostAura::RemovedFromWidget()
{
    if (host_->native_view()) {
        host_->native_view()->Hide();
        host_->native_view()->ClearProperty(aura::client::kHostWindowKey);
        if (host_->native_view()->parent())
            host_->native_view()->parent()->RemoveChild(host_->native_view());
        RemoveClippingWindow();
    }
}

void NativeViewHostAura::InstallClip(int x, int y, int w, int h)
{
    clip_rect_.reset(
        new gfx::Rect(host_->ConvertRectToWidget(gfx::Rect(x, y, w, h))));
}

bool NativeViewHostAura::HasInstalledClip()
{
    return !!clip_rect_;
}

void NativeViewHostAura::UninstallClip()
{
    clip_rect_.reset();
}

void NativeViewHostAura::ShowWidget(int x, int y, int w, int h)
{
    int width = w;
    int height = h;
    if (host_->fast_resize()) {
        gfx::Point origin(x, y);
        views::View::ConvertPointFromWidget(host_, &origin);
        InstallClip(origin.x(), origin.y(), w, h);
        width = host_->native_view()->bounds().width();
        height = host_->native_view()->bounds().height();
    }
    clipping_window_.SetBounds(clip_rect_ ? *clip_rect_
                                          : gfx::Rect(x, y, w, h));

    gfx::Point clip_offset = clipping_window_.bounds().origin();
    host_->native_view()->SetBounds(
        gfx::Rect(x - clip_offset.x(), y - clip_offset.y(), width, height));
    host_->native_view()->Show();
    clipping_window_.Show();
}

void NativeViewHostAura::HideWidget()
{
    host_->native_view()->Hide();
    clipping_window_.Hide();
}

void NativeViewHostAura::SetFocus()
{
    aura::Window* window = host_->native_view();
    aura::client::FocusClient* client = aura::client::GetFocusClient(window);
    if (client)
        client->FocusWindow(window);
}

gfx::NativeViewAccessible NativeViewHostAura::GetNativeViewAccessible()
{
    return NULL;
}

gfx::NativeCursor NativeViewHostAura::GetCursor(int x, int y)
{
    if (host_->native_view())
        return host_->native_view()->GetCursor(gfx::Point(x, y));
    return gfx::kNullCursor;
}

void NativeViewHostAura::OnWindowDestroying(aura::Window* window)
{
    DCHECK(window == host_->native_view());
    clipping_window_delegate_->set_native_view(NULL);
}

void NativeViewHostAura::OnWindowDestroyed(aura::Window* window)
{
    DCHECK(window == host_->native_view());
    host_->NativeViewDestroyed();
}

// static
NativeViewHostWrapper* NativeViewHostWrapper::CreateWrapper(
    NativeViewHost* host)
{
    return new NativeViewHostAura(host);
}

void NativeViewHostAura::AddClippingWindow()
{
    RemoveClippingWindow();

    host_->native_view()->SetProperty(aura::client::kHostWindowKey,
        host_->GetWidget()->GetNativeView());
    Widget::ReparentNativeView(host_->native_view(),
        &clipping_window_);
    if (host_->GetWidget()->GetNativeView()) {
        Widget::ReparentNativeView(&clipping_window_,
            host_->GetWidget()->GetNativeView());
    }
}

void NativeViewHostAura::RemoveClippingWindow()
{
    clipping_window_.Hide();
    if (host_->native_view())
        host_->native_view()->ClearProperty(aura::client::kHostWindowKey);

    if (host_->native_view()->parent() == &clipping_window_) {
        if (host_->GetWidget() && host_->GetWidget()->GetNativeView()) {
            Widget::ReparentNativeView(host_->native_view(),
                host_->GetWidget()->GetNativeView());
        } else {
            clipping_window_.RemoveChild(host_->native_view());
        }
        host_->native_view()->SetBounds(clipping_window_.bounds());
    }
    if (clipping_window_.parent())
        clipping_window_.parent()->RemoveChild(&clipping_window_);
}

} // namespace views
